%% The contents of this file are subject to the Mozilla Public License
%% Version 1.1 (the "License"); you may not use this file except in
%% compliance with the License. You may obtain a copy of the License
%% at http://www.mozilla.org/MPL/
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and
%% limitations under the License.
%%
%% The Original Code is RabbitMQ.
%%
%% The Initial Developer of the Original Code is GoPivotal, Inc.
%% Copyright (c) 2007-2014 GoPivotal, Inc.  All rights reserved.
%%

-module(rabbit_vhost).

-include("rabbit.hrl").

%%----------------------------------------------------------------------------

-export([add/1, delete/1, exists/1, list/0, with/2, assert/1]).
-export([info/1, info/2, info_all/0, info_all/1]).

-ifdef(use_specs).

-spec(add/1 :: (rabbit_types:vhost()) -> 'ok').
-spec(delete/1 :: (rabbit_types:vhost()) -> 'ok').
-spec(exists/1 :: (rabbit_types:vhost()) -> boolean()).
-spec(list/0 :: () -> [rabbit_types:vhost()]).
-spec(with/2 :: (rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A).
-spec(assert/1 :: (rabbit_types:vhost()) -> 'ok').

-spec(info/1 :: (rabbit_types:vhost()) -> rabbit_types:infos()).
-spec(info/2 :: (rabbit_types:vhost(), rabbit_types:info_keys())
                -> rabbit_types:infos()).
-spec(info_all/0 :: () -> [rabbit_types:infos()]).
-spec(info_all/1 :: (rabbit_types:info_keys()) -> [rabbit_types:infos()]).

-endif.

%%----------------------------------------------------------------------------

-define(INFO_KEYS, [name, tracing]).

%% RabbitMQ系统新增一个VirtualHost的接口
add(VHostPath) ->
	%% 打印增加VirtualHost的日志
	rabbit_log:info("Adding vhost '~s'~n", [VHostPath]),
	%% 将VirtualHost的数据结构vhost写入rabbit_vhost mnesia数据库表
	R = rabbit_misc:execute_mnesia_transaction(
		  fun () ->
				   %% 从rabbit_vhost mnesia数据库表中读取VHostPath
				   case mnesia:wread({rabbit_vhost, VHostPath}) of
					   %% 如果没有读到该VHostPath信息，则将VHostPath插入rabbit_vhost表
					   []  -> ok = mnesia:write(rabbit_vhost,
												#vhost{virtual_host = VHostPath},
												write);
					   [_] -> mnesia:abort({vhost_already_exists, VHostPath})
				   end
		  end,
		  fun (ok, true) ->
				   ok;
			 (ok, false) ->
				  %% 添加默认的exchange交换机信息
				  [rabbit_exchange:declare(
					 rabbit_misc:r(VHostPath, exchange, Name),
					 Type, true, false, Internal, []) ||
					 {Name, Type, Internal} <-
						 [{<<"">>,                   direct,  false},
						  {<<"amq.direct">>,         direct,  false},
						  {<<"amq.topic">>,          topic,   false},
						  %% per 0-9-1 pdf
						  {<<"amq.match">>,          headers, false},
						  %% per 0-9-1 xml
						  {<<"amq.headers">>,        headers, false},
						  {<<"amq.fanout">>,         fanout,  false},
						  %% 跟踪模块使用的amq.rabbitmq.trace交换机
						  {<<"amq.rabbitmq.trace">>, topic,   true}]],
				  ok
		  end),
	%% 发布VirtualHost创建成功的事件
	rabbit_event:notify(vhost_created, info(VHostPath)),
	R.


%% 删除VHost的接口
delete(VHostPath) ->
	%% FIXME: We are forced to delete the queues and exchanges outside
	%% the TX below. Queue deletion involves sending messages to the queue
	%% process, which in turn results in further mnesia actions and
	%% eventually the termination of that process. Exchange deletion causes
	%% notifications which must be sent outside the TX
	%% 打印删除VHost的日志
	rabbit_log:info("Deleting vhost '~s'~n", [VHostPath]),
	%% 删除队列的函数，直接让队列进程停止掉
	QDelFun = fun (Q) -> rabbit_amqqueue:delete(Q, false, false) end,
	%% 删除VHost下面的所有队列
	[assert_benign(rabbit_amqqueue:with(Name, QDelFun)) ||
	   #amqqueue{name = Name} <- rabbit_amqqueue:list(VHostPath)],
	%% 删除VHost下面所有的exchange交换机
	[assert_benign(rabbit_exchange:delete(Name, false)) ||
	   #exchange{name = Name} <- rabbit_exchange:list(VHostPath)],
	%% 最后是实际删除Vhost的操作
	Funs = rabbit_misc:execute_mnesia_transaction(
			 with(VHostPath, fun () -> internal_delete(VHostPath) end)),
	%% rabbit_evet发布VHost删除的日志
	ok = rabbit_event:notify(vhost_deleted, [{name, VHostPath}]),
	[ok = Fun() || Fun <- Funs],
	ok.


%% 对删除队列或者exchange的返回值进行断言操作
assert_benign(ok)                 -> ok;

assert_benign({ok, _})            -> ok;

assert_benign({error, not_found}) -> ok;

assert_benign({error, {absent, Q, _}}) ->
	%% Removing the mnesia entries here is safe. If/when the down node
	%% restarts, it will clear out the on-disk storage of the queue.
	case rabbit_amqqueue:internal_delete(Q#amqqueue.name) of
		ok                 -> ok;
		{error, not_found} -> ok
	end.


%% 内部删除VHost的接口
internal_delete(VHostPath) ->
	%% 清除掉VHostPath下所有的用户的权限信息
	[ok = rabbit_auth_backend_internal:clear_permissions(
			proplists:get_value(user, Info), VHostPath)
			|| Info <- rabbit_auth_backend_internal:list_vhost_permissions(VHostPath)],
	Fs1 = [rabbit_runtime_parameters:clear(VHostPath,
										   proplists:get_value(component, Info),
										   proplists:get_value(name, Info))
			 || Info <- rabbit_runtime_parameters:list(VHostPath)],
	Fs2 = [rabbit_policy:delete(VHostPath, proplists:get_value(name, Info))
			 || Info <- rabbit_policy:list(VHostPath)],
	%% 最后删除rabbit_vhost数据表中的数据
	ok = mnesia:delete({rabbit_vhost, VHostPath}),
	Fs1 ++ Fs2.


%% 判断VirtualHost是否存在
exists(VHostPath) ->
	mnesia:dirty_read({rabbit_vhost, VHostPath}) /= [].


%% 拿到RabbitMQ当前所有的VirtualHost名字列表
list() ->
	mnesia:dirty_all_keys(rabbit_vhost).


%% 判断VirtualHost是否存在的函数，如果存在则执行Thunk函数
with(VHostPath, Thunk) ->
	fun () ->
			 case mnesia:read({rabbit_vhost, VHostPath}) of
				 [] ->
					 mnesia:abort({no_such_vhost, VHostPath});
				 [_V] ->
					 Thunk()
			 end
	end.


%% Like with/2 but outside an Mnesia tx
%% 断言判断VirtualHost是否存在
assert(VHostPath) -> case exists(VHostPath) of
						 true  -> ok;
						 false -> throw({error, {no_such_vhost, VHostPath}})
					 end.

%%----------------------------------------------------------------------------

infos(Items, X) -> [{Item, i(Item, X)} || Item <- Items].


i(name,    VHost) -> VHost;

i(tracing, VHost) -> rabbit_trace:enabled(VHost);

i(Item, _)        -> throw({bad_argument, Item}).


info(VHost)        -> infos(?INFO_KEYS, VHost).


info(VHost, Items) -> infos(Items, VHost).


info_all()      -> info_all(?INFO_KEYS).


info_all(Items) -> [info(VHost, Items) || VHost <- list()].
